﻿using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;

namespace DatabaseGroup.SSIS.SourceAdapters
{
    /// <summary>
    /// Contains common SSIS component helper methods
    /// </summary>
    public class CommonUtils
    {
        /// <summary>
        /// Examines two validation status values and returns the overall result
        /// </summary>
        /// <param name="oldStatus">The old validation status</param>
        /// <param name="newStatus">The latest validation status</param>
        /// <returns>The overall validation status based on the two arguments</returns>
        public static DTSValidationStatus CompareValidationValues(DTSValidationStatus oldStatus, DTSValidationStatus newStatus)
        {
            if (oldStatus == DTSValidationStatus.VS_ISVALID && newStatus == DTSValidationStatus.VS_ISVALID)
                return DTSValidationStatus.VS_ISVALID;

            if (oldStatus == DTSValidationStatus.VS_ISCORRUPT || newStatus == DTSValidationStatus.VS_ISCORRUPT)
                return DTSValidationStatus.VS_ISCORRUPT;

            return DTSValidationStatus.VS_ISBROKEN;
        }

        /// <summary>
        /// Utility method to only take the first x characters of a string
        /// from left to right
        /// </summary>
        /// <param name="source">The source string to take the characters from</param>
        /// <param name="maxLength">The maximum number of characters to take</param>
        /// <returns>The (possibly) truncated string</returns>
        public static string LimitStringLength(string source, int maxLength)
        {
            if (string.IsNullOrEmpty(source) || source.Length <= maxLength)
                return source;

            return source.Substring(0, maxLength);
        }

        /// <summary>
        /// Deserializes a block of XML containing a list of configured file columns
        /// </summary>
        /// <param name="columnsXML">The XML string containing the list of column configurations</param>
        /// <returns>The deserialized list of column configurations</returns>
        public static List<ConfiguredFileColumn> DeserializeConfiguredFileColumns(string columnsXML)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(ConfiguredFileColumn[]));
            List<ConfiguredFileColumn> configuredFileColumns = new List<ConfiguredFileColumn>();

            foreach (var column in (ConfiguredFileColumn[])serializer.Deserialize(new StringReader(columnsXML)))
                configuredFileColumns.Add(column);

            return configuredFileColumns;
        }

        /// <summary>
        /// Serializes a list of configured file columns
        /// </summary>
        /// <param name="columns">The list of configured file columns</param>
        /// <returns>String of the serialized list of columns</returns>
        public static string SerializeConfiguredFileColumns(List<ConfiguredFileColumn> columns)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(ConfiguredFileColumn[]));
            StringWriter columnsXML = new StringWriter();
            serializer.Serialize(columnsXML, columns.ToArray());

            return columnsXML.ToString();
        }

        /// <summary>
        /// Adds a new column to a specific output
        /// </summary>
        /// <param name="output">The output to add the column to</param>
        /// <param name="columnName">The name for the new column</param>
        /// <param name="type">The datatype of the new column</param>
        /// <param name="length">The length of the new column (for text datatypes)</param>
        /// <param name="precision">The precision of the new column (for decimal related datatypes)</param>
        /// <param name="scale">The scale of the new column (for decimal related datatypes)</param>
        /// <param name="codePage">The code page of the new column (for text datatypes)</param>
        public static void AddOutputColumn(IDTSOutput90 output, string columnName, DataType type, int length, int precision, int scale, int codePage)
        {
            IDTSOutputColumnCollection90 outputColumnCollection = output.OutputColumnCollection;
            IDTSOutputColumn90 outputColumn = outputColumnCollection.New();
            outputColumn.Name = columnName;
            outputColumn.SetDataTypeProperties(type, length, precision, scale, codePage);
        }

        /// <summary>
        /// Configures the component error output audit columns
        /// using the preferences provided
        /// </summary>
        /// <param name="output">The error output to configure</param>
        /// <param name="includeErrorAuditing">True if error log auditing should be included</param>
        /// <param name="includeValidationMessages">True if verbose validation messages should be included</param>
        /// <param name="includeSourceData">True if the source data which triggered the error should be included</param>
        /// <param name="sourceDataType">The generic data type to use for source data</param>
        /// <param name="sourceDataSize">The maximum size of source data</param>
        public static void ConfigureErrorOutputAuditing(IDTSOutput90 output, bool includeErrorAuditing, bool includeValidationMessages,
            bool includeSourceData, DataType sourceDataType, int sourceDataSize)
        {
            bool removeAuditColumn = !includeErrorAuditing;
            ManageOutputColumn(removeAuditColumn, output, DelimitedFileSource2005Component.LineNumberColumnName, DataType.DT_I8, 0, 0, 0, 0);
            ManageOutputColumn(removeAuditColumn, output, DelimitedFileSource2005Component.OccurredColumnName, DataType.DT_DBTIMESTAMP, 0, 0, 0, 0);
            ManageOutputColumn(removeAuditColumn, output, DelimitedFileSource2005Component.IsInvalidColumnName, DataType.DT_BOOL, 0, 0, 0, 0);
            ManageOutputColumn(removeAuditColumn, output, DelimitedFileSource2005Component.TargetColumnDataTypeColumnName, DataType.DT_STR, 20, 0, 0, PropertiesManager.DefaultCodePage);
            ManageOutputColumn(removeAuditColumn, output, DelimitedFileSource2005Component.TargetColumnLengthColumnName, DataType.DT_I4, 0, 0, 0, 0);

            bool removeValidationMessages = (!includeErrorAuditing || !includeValidationMessages);
            ManageOutputColumn(removeValidationMessages, output, DelimitedFileSource2005Component.DescriptionColumnName,
                DataType.DT_STR, 255, 0, 0, PropertiesManager.DefaultCodePage);

            int codepage = 0;

            // TODO: use configured codepage instead of default
            if (sourceDataType == DataType.DT_STR || sourceDataType == DataType.DT_TEXT)
                codepage = PropertiesManager.DefaultCodePage;

            bool removeSourceData = (!includeErrorAuditing || !includeSourceData);
            ManageOutputColumn(removeSourceData, output, DelimitedFileSource2005Component.SourceColumnDataColumnName,
                sourceDataType, sourceDataSize, 0, 0, codepage);
            ManageOutputColumn(removeSourceData, output, DelimitedFileSource2005Component.RowDataColumnName,
                sourceDataType, sourceDataSize, 0, 0, codepage);
        }

        /// <summary>
        /// Configures the log output of the component based on the provided preferences
        /// </summary>
        /// <param name="output">The log output to configure</param>
        /// <param name="includeValidationMessages">True if verbose validation messages should be included</param>
        /// <param name="includeSourceData">True if the source data which triggered the error should be included</param>
        /// <param name="sourceDataType">The generic data type to use for source data</param>
        /// <param name="sourceDataSize">The maximum size of source data</param>
        public static void ConfigureLogOutput(IDTSOutput90 output, bool includeValidationMessages,
            bool includeSourceData, DataType sourceDataType, int sourceDataSize)
        {
            ManageOutputColumn(false, output, DelimitedFileSource2005Component.LineNumberColumnName, DataType.DT_I8, 0, 0, 0, 0);
            ManageOutputColumn(false, output, DelimitedFileSource2005Component.ErrorCodeColumnName, DataType.DT_I4, 0, 0, 0, 0);
            ManageOutputColumn(false, output, DelimitedFileSource2005Component.OccurredColumnName, DataType.DT_DBTIMESTAMP, 0, 0, 0, 0);
            ManageOutputColumn(false, output, DelimitedFileSource2005Component.SourceColumnName, DataType.DT_STR, 255, 0, 0, PropertiesManager.DefaultCodePage);
            ManageOutputColumn(false, output, DelimitedFileSource2005Component.TargetColumnDataTypeColumnName, DataType.DT_STR, 20, 0, 0, PropertiesManager.DefaultCodePage);
            ManageOutputColumn(false, output, DelimitedFileSource2005Component.TargetColumnLengthColumnName, DataType.DT_I4, 0, 0, 0, 0);
            
            bool removeValidationMessages = !includeValidationMessages;
            ManageOutputColumn(removeValidationMessages, output, DelimitedFileSource2005Component.ValidationMessageColumnName,
                DataType.DT_STR, 255, 0, 0, PropertiesManager.DefaultCodePage);

            int codepage = 0;

            // TODO: use configured codepage instead of default
            if (sourceDataType == DataType.DT_STR || sourceDataType == DataType.DT_TEXT)
                codepage = PropertiesManager.DefaultCodePage;

            bool removeSourceData = !includeSourceData;
            ManageOutputColumn(removeSourceData, output, DelimitedFileSource2005Component.SourceColumnDataColumnName,
                sourceDataType, sourceDataSize, 0, 0, codepage);
        }

        /// <summary>
        /// Attempts to locate an output column of a specific name
        /// </summary>
        /// <param name="output">The output to search</param>
        /// <param name="name">The column name to find</param>
        /// <returns>The matching column or null when not matched</returns>
        public static IDTSOutputColumn90 FindOutputColumnByName(IDTSOutput90 output, string name)
        {
            foreach (IDTSOutputColumn90 column in output.OutputColumnCollection)
            {
                if (column.Name == name)
                    return column;
            }

            return null;
        }

        /// <summary>
        /// Manages the adding/removing or updating of an output column
        /// </summary>
        /// <param name="deleteColumn">True if the column should be deleted</param>
        /// <param name="output">The output the column relates to</param>
        /// <param name="columnName">The name of the column</param>
        /// <param name="type">The DataType of the column</param>
        /// <param name="length">The max length of the column</param>
        /// <param name="precision">The precision of the column</param>
        /// <param name="scale">The scale of the column</param>
        /// <param name="codePage">The column codepage</param>
        public static void ManageOutputColumn(bool deleteColumn, IDTSOutput90 output,
            string columnName, DataType type, int length, int precision, int scale, int codePage)
        {
            IDTSOutputColumn90 column = FindOutputColumnByName(output, columnName);

            if (deleteColumn && column != null)
            {
                output.OutputColumnCollection.RemoveObjectByID(column.ID);
                return;
            }
            
            if (!deleteColumn && column != null)
            {
                column.SetDataTypeProperties(type, length, precision, scale, codePage);
            }
            else if (!deleteColumn)
            {
                AddOutputColumn(output, columnName, type, length, precision, scale, codePage);
            }
        }
    }
}
